/**
 * User: alesj
 * Date: 2005.3.11
 * Time: 14:51:31
 * 
 * (C) Genera Lynx d.o.o.
 */

package com.generalynx.ecos.data.types;

import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.usertype.UserType;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.util.ReflectHelper;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

/**
 * A type for Hibernate <tt>PersistentEnum</tt>
 *
 * @author Gavin King
 * @author Ales Justin
 * @see com.generalynx.ecos.data.types.PersistentEnum
 */
public abstract class PersistentEnumType implements UserType, ParameterizedType {

    private static final Class[] OBJECT_ARG = new Class[]{Object.class};

    private Class enumClass;
    private Method method;

    public void setParameterValues(Properties parameters) {
        String enumClassName = parameters.getProperty("enumClass");
        if (enumClassName == null) {
            throw new MappingException("Type definition must have enum Class defined!");
        }
        try {
            this.enumClass = ReflectHelper.classForName(enumClassName);
            method = enumClass.getDeclaredMethod("fromSwitchCode", OBJECT_ARG);
            if (!ReflectHelper.isPublic(enumClass, method)) {
                method.setAccessible(true);
            }
        } catch (ClassNotFoundException cnf) {
            throw new MappingException(cnf);
        } catch (NoSuchMethodException nme) {
            throw new MappingException("PersistentEnum class did not implement fromSwitchCode: " + enumClass.getName());
        }
    }

    protected abstract int sqlType();

    protected abstract void setNotNull(PreparedStatement st, Object value, int index) throws SQLException;

    public Object getInstance(Object code) throws HibernateException {
        try {
            return method.invoke(null, new Object[]{code});
        } catch (IllegalArgumentException iae) {
            throw new AssertionFailure("Could not invoke fromSwitchCode() from PersistentEnumType", iae);
        } catch (InvocationTargetException ite) {
            throw new HibernateException("InvocationTargetException occurred inside fromSwitchCode()", ite);
        } catch (IllegalAccessException iae) {
            throw new HibernateException("IllegalAccessException occurred calling fromSwitchCode()", iae);
        }
    }

    public boolean equals(Object x, Object y) {
        return (x == y) ||
                (x != null && y != null && x.getClass() == y.getClass() &&
                ((PersistentEnum)x).toSwitchCode() == ((PersistentEnum)y).toSwitchCode());
    }

    public boolean equals(Object object) {
        if (!(object == this || (object != null && object.getClass() == getClass()))) {
            return false;
        }
        return ((PersistentEnumType)object).enumClass == enumClass;
    }

    public int hashCode() {
        return enumClass.hashCode();
    }

    public int[] sqlTypes() {
        return new int[]{sqlType()};
    }

    public Class returnedClass() {
        return enumClass;
    }

    public int hashCode(Object x) throws HibernateException {
        return hashCode();
    }

    public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
            throws HibernateException, SQLException {
        Object code = rs.getObject(names[0]);
        return (rs.wasNull() ? null : getInstance(code));
    }

    public void nullSafeSet(PreparedStatement st, Object value, int index)
            throws HibernateException, SQLException {
        if (value == null) {
            st.setNull(index, sqlType());
        } else {
            setNotNull(st, value, index);
        }
    }

    public Object deepCopy(Object value) throws HibernateException {
        return value;
    }

    public boolean isMutable() {
        return false;
    }

    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        if (cached == null) {
            return null;
        } else {
            return getInstance(cached);
        }
    }

    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return original;
    }
}
